package net.gdface.facelog;

import static com.google.common.base.Preconditions.*;
import static gu.sql2java.Managers.*;
import static net.gdface.facelog.FeatureConfig.*;
import java.io.IOException;
import java.lang.reflect.Array;
import java.lang.reflect.InvocationTargetException;
import java.nio.ByteBuffer;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Calendar;
import java.util.Collection;
import java.util.Collections;
import java.util.Date;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.Map.Entry;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.atomic.AtomicReference;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import org.apache.commons.lang3.tuple.Pair;

import com.google.common.base.Function;
import com.google.common.base.Joiner;
import com.google.common.base.MoreObjects;
import com.google.common.base.Objects;
import com.google.common.base.Optional;
import com.google.common.base.Predicate;
import com.google.common.base.Predicates;
import com.google.common.base.Strings;
import com.google.common.base.Throwables;
import com.google.common.collect.Collections2;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.Iterables;
import com.google.common.collect.Iterators;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.google.common.collect.Sets;
import gu.sql2java.BaseBean;
import gu.sql2java.ColumnGetter;
import gu.sql2java.RowMetaData;
import gu.sql2java.TableManager;
import gu.sql2java.exception.ObjectRetrievalException;
import gu.sql2java.exception.RuntimeDaoException;
import net.gdface.facelog.db.Constant;
import net.gdface.facelog.db.DeviceBean;
import net.gdface.facelog.db.DeviceGroupBean;
import net.gdface.facelog.db.ErrorLogBean;
import net.gdface.facelog.db.FaceBean;
import net.gdface.facelog.db.FeatureBean;
import net.gdface.facelog.db.IDeviceGroupManager;
import net.gdface.facelog.db.IPermitManager;
import net.gdface.facelog.db.IPersonGroupManager;
import net.gdface.facelog.db.ImageBean;
import net.gdface.facelog.db.LogBean;
import net.gdface.facelog.db.PermitBean;
import net.gdface.facelog.db.PersonBean;
import net.gdface.facelog.db.PersonGroupBean;
import net.gdface.facelog.db.StoreBean;
import net.gdface.image.LazyImage;
import net.gdface.image.NotImageException;
import net.gdface.image.UnsupportedFormatException;
import net.gdface.utils.Assert;
import net.gdface.utils.BinaryUtils;
import net.gdface.utils.Judge;
import net.gdface.utils.MiscellaneousUtils;

/**
 * 数据库操作扩展
 * @author guyadong
 *
 */
public class DaoManagement extends BaseDao implements ServiceConstant,Constant{
	private final CryptographGenerator cg;
	public DaoManagement(CryptographGenerator cg) {
		this.cg = checkNotNull(cg,"cg is null");
	}
	public DaoManagement() {
		this(CryptographBySalt.INSTANCE);
	}
	public CryptographGenerator getCryptographGenerator() {
		return cg;
	}

	/** 检查姓名是否有效,不允许使用保留字{@code root} ,无效抛出{@link IllegalArgumentException} 异常 */
	protected static void checkPersonName(PersonBean personBean){
		checkArgument(null == personBean || !ROOT_NAME.equals(personBean.getName()),
				"INVALID person name:%s, reserved word",ROOT_NAME);
	}
	/** 
	 * 增加人员姓名检查,参见 {@link #checkPersonName(PersonBean)}<br>
	 * 增加 password密文更新
	 * @throws IllegalStateException {@code password}不是有效的MD5字符串
	 */
	@Override
	protected PersonBean daoSavePerson(PersonBean personBean) throws RuntimeDaoException {
		checkPersonName(personBean);
		if(personBean.checkPasswordModified()){
			String password = personBean.getPassword();
			if(null != password){
				checkState(BinaryUtils.validMd5(password),"password field must be MD5 string(32 char,lower case)");
				// 重新生成password加盐密文
				personBean.setPassword(cg.cryptograph(password, true));
			}
		}
		return super.daoSavePerson(personBean);
	}

	protected static StoreBean makeStoreBean(ByteBuffer imageBytes,String md5,String encodeing){
		if(Judge.isEmpty(imageBytes)){
			return null;
		}
		if(null == md5){
			md5 = BinaryUtils.getMD5String(imageBytes);
		}
		StoreBean storeBean = new StoreBean();
		storeBean.setData(imageBytes);
		storeBean.setMd5(md5);
		if(!Strings.isNullOrEmpty(encodeing)){
			storeBean.setEncoding(encodeing);
		}
		return storeBean;
	}

	/////////////////////PERMIT////////////////////
	
	/**
	 * 获取人员组通行权限<br>
	 * 返回{@code personGroupId}指定的人员组在{@code deviceGroupId}指定的设备组上是否允许通行,
	 * 本方法会对{@code deviceGroupId}的子结点向下递归：
	 * {@code personGroupId } 及其子结点,任何一个在permit表存在与{@code deviceId}所属设备级的关联记录中就返回true，
	 * 输入参数为{@code null}或找不到指定的记录则返回false
	 * @param deviceGroupId
	 * @param personGroupId
	 * @return 允许通行返回指定的{@link PermitBean}记录，否则返回{@code null}
	 */
	private PermitBean daoGetPermitOnPersonGroup(Integer deviceGroupId, final Integer personGroupId) throws RuntimeDaoException {
		DeviceGroupBean deviceGroup;
		if(null == deviceGroupId
			|| null == personGroupId 
			|| null == (deviceGroup = daoGetDeviceGroup(deviceGroupId))){
			return null;
		}
		for(DeviceGroupBean group : daoChildListByParentForDeviceGroup(deviceGroup)){
			PermitBean permit = daoGetPermit(group.getId(), personGroupId);
			if(permit != null){
				return permit;
			}
		}
		return null;
	}
	/**
	 * 获取人员组通行权限<br>
	 * 返回{@code personGroupId}指定的人员组在{@code deviceGroupId}指定的设备组上是否允许通行,
	 * 本方法会对{@code personGroupId}的父结点向上回溯：
	 * {@code personGroupId } 及其父结点,任何一个在permit表存在与{@code deviceId}所属设备级的关联记录中就返回true，
	 * 输入参数为{@code null}或找不到指定的记录则返回false
	 * @param deviceGroupId
	 * @param personGroupId
	 * @return 允许通行返回指定的{@link PermitBean}记录，否则返回{@code null}
	 */
	protected PermitBean daoGetGroupPermitOnDeviceGroup(final Integer deviceGroupId,Integer personGroupId){
		PersonGroupBean personGroup;
		if(null == deviceGroupId
			|| null == personGroupId 
			|| null == (personGroup = daoGetPersonGroup(personGroupId))){
			return null;
		}
		List<PersonGroupBean> personGroupList = daoListOfParentForPersonGroup(personGroup);
		// first is self
		Collections.reverse(personGroupList);
		for(PersonGroupBean group : personGroupList){
			PermitBean permit = daoGetPermitOnPersonGroup(deviceGroupId,group.getId());
			if(permit != null){
				return permit;
			}
		}
		return null;
	}
	/**
	 * 获取人员组通行权限<br>
	 * 返回{@code personGroupId}指定的人员组在{@code deviceId}设备上是否允许通行,
	 * 本方法会对{@code personGroupId}的父结点向上回溯：
	 * {@code personGroupId } 及其父结点,任何一个在permit表存在与{@code deviceId}所属设备级的关联记录中就返回true，
	 * 输入参数为{@code null}或找不到指定的记录则返回false
	 * @param deviceId
	 * @param personGroupId
	 * @return 允许通行返回指定的{@link PermitBean}记录，否则返回{@code null}
	 * @see #daoGetGroupPermitOnDeviceGroup(Integer, Integer)
	 */
	protected PermitBean daoGetGroupPermit(Integer deviceId,Integer personGroupId){
		DeviceBean device;
		if(null == deviceId || null ==(device = daoGetDevice(deviceId))){
			return null;
		}
		return daoGetGroupPermitOnDeviceGroup(device.getGroupId(),personGroupId);
	}
	protected PermitBean daoGetPersonPermit(Integer deviceId,Integer personId){
		PersonBean person;
		if( null == personId || null == (person = daoGetPerson(personId))){
			return null;
		}
		return daoGetGroupPermit(deviceId,person.getGroupId());
	}
	/**
	 * 判断指定用户是否可以在指定设备上通行
	 * @param personId 用户ID
	 * @param deviceId 设备ID
	 * @return 允许通行返回{@code true},否则返回{@code false}
	 */
	protected boolean daoIsPermit(int personId,int deviceId){
		Set<PersonBean> features = daoGetPersonsPermittedOnDevice(deviceId, false,  null, null);
		return Collections2.transform(features, daoCastPersonToPk).contains(personId);
	}
	protected List<PermitBean> daoGetGroupPermit(final Integer deviceId,List<Integer> personGroupIdList){
		if(null == deviceId || null == personGroupIdList){
			return Collections.emptyList();
		}
		return Lists.newArrayList(Lists.transform(personGroupIdList, new Function<Integer,PermitBean>(){
			@Override
			public PermitBean apply(Integer input) {
				return daoGetGroupPermit(deviceId,input);
			}}));
	}
	protected List<PermitBean> daoGetPermit(final Integer deviceId,List<Integer> personIdList){
		if(null == deviceId || null == personIdList){
			return Collections.emptyList();
		}
		return Lists.newArrayList(Lists.transform(personIdList, new Function<Integer,PermitBean>(){
			@Override
			public PermitBean apply(Integer input) {
				return daoGetPersonPermit(deviceId,input);
			}}));
	}
	/**
	 * 从permit表返回允许在{@code deviceGroupId}指定的设备组通过的所有人员组({@link PersonGroupBean})对象的id
	 * @param deviceGroupId 为{@code null}返回空表
	 * @param ignoreSchedule 是否忽略时间过滤器(fl_permit.schedule字段)的限制
	 * @return 人员组id列表
	 */
	protected List<Integer> daoGetPersonGroupsPermittedBy(Integer deviceGroupId,boolean ignoreSchedule){
		if(deviceGroupId == null){
			return Collections.emptyList();
		}
		Set<PersonGroupBean> permittedGroups = Sets.newHashSet();
		DateTimeJsonFilter shedule = new DateTimeJsonFilter();
		final Date date = new Date();
		for(PermitBean permit : daoGetPermitBeansByDeviceGroupIdOnDeviceGroup(deviceGroupId)){
			if(ignoreSchedule || shedule.apply(date,permit.getSchedule())){
				permittedGroups.addAll(daoChildListByParentForPersonGroup(permit.getPersonGroupId()));
			}
		}
		return Lists.newArrayList(Iterables.transform(permittedGroups, daoCastPersonGroupToPk));
	}
	/**
	 * 从permit表返回允许在{@code deviceGroupId}指定的设备组通过的所有人员组({@link PersonGroupBean})对象的id,
	 * 忽略时间过滤器(fl_permit.schedule字段)的限制
	 * @param deviceGroupId 为{@code null}返回空表
	 * @return 人员组id列表
	 */
	protected List<Integer> daoGetPersonGroupsPermittedBy(Integer deviceGroupId){
		if(deviceGroupId == null){
			return Collections.emptyList();
		}
		List<PermitBean> permits = daoGetPermitBeansByDeviceGroupIdOnDeviceGroup(deviceGroupId);		
		return Lists.transform(permits, daoCastPermitToPersonGroupId);
	}
	/**
	 * 从permit表返回允许{@code personGroupId}指定的人员组通过的所有设备组({@link DeviceGroupBean})对象的id
	 * @param personGroupId 为{@code null}返回空表
	 * @return 设备组id列表
	 */
	protected List<Integer> daoGetDeviceGroupsPermittedBy(Integer personGroupId){
		if(personGroupId == null){
			return Collections.emptyList();
		}
		List<PermitBean> permits = daoGetPermitBeansByPersonGroupIdOnPersonGroup(personGroupId);
		return Lists.transform(permits, daoCastPermitToDeviceGroupId);
	}
	/**
	 * 从permit表返回允许在{@code personGroupId}指定的人员组通过的所有设备组({@link DeviceGroupBean})的id<br>
	 * 不排序,不包含重复id
	 * @param personGroupId 为{@code null}返回空表
	 * @return 设备组id列表
	 */
	protected List<Integer> daoGetDeviceGroupsPermit(Integer personGroupId){
		if(personGroupId == null){
			return Collections.emptyList();
		}
		PermitBean template = PermitBean.builder().personGroupId(personGroupId).build();
		List<PermitBean> permits = daoLoadPermitUsingTemplate(template, 1, -1);
		HashSet<Integer> groups = Sets.newHashSet();
		for (PermitBean bean : permits) {
			groups.addAll(Lists.transform(daoListOfParentForDeviceGroup(bean.getDeviceGroupId()),daoCastDeviceGroupToPk));
		}
		return Lists.newArrayList(groups);
	}
	
	/**
	 * 从permit表删除指定{@code personGroupId}指定人员组的在所有设备上的通行权限
	 * @param personGroupId 为{@code null}返回0
	 * @return 删除的记录条数
	 */
	protected int daoDeletePersonGroupPermit(Integer personGroupId) {
		if(personGroupId == null){
			return 0;
		}
		PermitBean template = PermitBean.builder().personGroupId(personGroupId).build();
		return instanceOf(IPermitManager.class).deleteUsingTemplate(template);
	}
	/**
	 * 从permit表删除指定{@code deviceGroupId}指定设备组上的人员通行权限
	 * @param deviceGroupId 为{@code null}返回0
	 * @return 删除的记录条数
	 */
	protected int daoDeleteGroupPermitOnDeviceGroup(Integer deviceGroupId) {
		if(deviceGroupId == null){
			return 0;
		}
		PermitBean template = PermitBean.builder().deviceGroupId(deviceGroupId).build();
		return instanceOf(IPermitManager.class).deleteUsingTemplate(template);
	}
	////////////////////////////////////////////
	
	/**
	 * 创建{@link ImageBean}对象,填充图像基本信息,同时创建对应的{@link StoreBean}对象
	 * @param imageBytes
	 * @param md5
	 * @return 返回 {@link ImageBean}和{@link StoreBean}对象
	 * @throws NotImageException
	 * @throws UnsupportedFormatException
	 */
	protected static Pair<ImageBean, StoreBean> makeImageBean(ByteBuffer imageBytes,String md5) throws NotImageException, UnsupportedFormatException{
		if(Judge.isEmpty(imageBytes)){
			return null;
		}
		LazyImage image = LazyImage.create(imageBytes);
		if(null == md5){
			md5 = BinaryUtils.getMD5String(imageBytes);
		}
		ImageBean imageBean = new ImageBean();
		imageBean.setMd5(md5);
		imageBean.setWidth(image.getWidth());
		imageBean.setHeight(image.getHeight());
		imageBean.setFormat(image.getSuffix());
		StoreBean storeBean = makeStoreBean(imageBytes, md5, null);
		return Pair.of(imageBean, storeBean);
	}
	protected ImageBean daoAddImage(ByteBuffer imageBytes,DeviceBean refFlDevicebyDeviceId
	        , Collection<FaceBean> impFlFacebyImgMd5 , Collection<PersonBean> impFlPersonbyImageMd5) throws DuplicateRecordException{
		if(Judge.isEmpty(imageBytes)){
			return null;
		}
		String md5 = BinaryUtils.getMD5String(imageBytes);
		daoCheckDuplicateImage(md5);
		Pair<ImageBean, StoreBean> pair;
		try {
			pair = makeImageBean(imageBytes,md5);
		} catch (Exception e) {
			throw new RuntimeException(e);
		}
		daoAddStore(pair.getRight());
		if(null != impFlFacebyImgMd5){
			pair.getLeft().setFaceNum(impFlFacebyImgMd5.size());
		}
		return daoAddImage(pair.getLeft(), refFlDevicebyDeviceId, impFlFacebyImgMd5,null, impFlPersonbyImageMd5);
	}
	/**
	 * (递归)删除imageMd5指定图像及其缩略图
	 * @param imageMd5
	 * @return 返回删除的记录条数(1),如果记录不存在返回0
	 */
	@Override
	protected int daoDeleteImage(String imageMd5){
		if(Strings.isNullOrEmpty(imageMd5)){
			return 0;
		}
		daoDeleteStore(imageMd5);
		ImageBean imageBean = daoGetImage(imageMd5);
		if(null == imageBean){return 0;}
		String thumbMd5 = imageBean.getThumbMd5();
		if( !Strings.isNullOrEmpty(thumbMd5)&& !imageBean.getMd5().equals(thumbMd5)){
			daoDeleteImage(thumbMd5);
		}
		return super.daoDeleteImage(imageMd5);
	}

	/**
	 * 根据配置定义的SDK算法参数,从特征数据中剥离出MD5计算范围的特征数据
	 * @param featureVersion
	 * @param feature
	 * @return MD5计算范围的特征数据
	 */
	private static byte[] stripFeature(String featureVersion,ByteBuffer feature){
	    int md5Off = FEATURE_CONFIG.md5OffsetOf(featureVersion);
	    int md5Len = FEATURE_CONFIG.md5LengthOf(featureVersion);
	    byte[] featureBytes = BinaryUtils.getBytesInBuffer(feature);
	    if(md5Len >= 0 && md5Len > 0 && (md5Off + md5Len) != featureBytes.length){
	    	// 检查参数有效性
	    	checkArgument((md5Off + md5Len) < featureBytes.length,
	    			"INVALID md5 range definition off:%s,len:%s for %s(feture size %s)", md5Off, md5Len,featureVersion,featureBytes.length);
    		return Arrays.copyOfRange(featureBytes, md5Off, md5Off + md5Len);
	    }
	    return featureBytes;
	}
	protected FeatureBean daoMakeFeature(ByteBuffer feature, String featureVersion){
		Assert.notEmpty(feature, "feature");
		// featureVersion不可为空
		checkArgument(!Strings.isNullOrEmpty(featureVersion),"featureVersion is null or empty");
	    // featureVersion内容只允许字母,数字,-,.,_符号
	    checkArgument(featureVersion.matches(SDK_VERSION_REGEX), "invalid sdk version format");
	    byte[] stripedFeature = stripFeature(featureVersion,feature);
	    String md5 = BinaryUtils.getMD5String(stripedFeature);   
		return FeatureBean.builder()
				.md5(md5)	
				.feature(feature)
				.version(featureVersion)
				.build();
	}
	
	protected byte[] daoGetFeatureBytes(String md5, boolean truncation) throws IOException{
		FeatureBean featureBean = daoGetFeature(md5);
		if(featureBean == null){
			return null;
		}
		if(!truncation){
			return BinaryUtils.getBytes(featureBean.getFeature());
		}
	    return stripFeature(featureBean.getVersion(),featureBean.getFeature());
	}
	
	protected List<byte[]> daoGetFeatureBytes(List<String> md5List, boolean truncation) throws IOException{
		List<byte[]> features = Lists.newLinkedList();
		if(md5List != null){
			for(String md5:md5List){
				byte[] feature = daoGetFeatureBytes(md5,truncation);
				features.add(feature == null ? new byte[0] : feature) ;
			}
		}
		return features;
	}
	/**
	 * 返回 persionId 关联的指定SDK的人脸特征记录
	 * @param personId 人员id(fl_person.id)
	 * @param featureVersion 算法(SDK)版本号
	 * @return 返回 fl_feature.md5  列表
	 */
	protected List<FeatureBean> daoGetFeaturesByPersonIdAndSdkVersion(int personId,String featureVersion) {
		FeatureBean tmpl = FeatureBean.builder().personId(personId).version(featureVersion).build();
		return daoLoadFeatureUsingTemplate(tmpl, 1, -1);
	}
	/**
	 * (递归)返回在指定设备上允许通行的所有人员记录<br>
	 * @param output 输出的用户对象列表,过滤所有有效期失效的用户
	 * @param deviceGroupId 设备组ID
	 * @param ignoreSchedule 是否忽略时间过滤器(fl_permit.schedule字段)的限制
	 * @param excludePersonIds 要排除的人员记录id,可为{@code null}
	 * @param timestamp 不为{@code null}时返回fl_person.expiry_date大于指定时间戳的所有fl_person记录
	 */
	private void 
	getPersonsPermittedOnDeviceGroup(Set<PersonBean>output,
			int deviceGroupId, 
			boolean ignoreSchedule, 
			List<Integer> excludePersonIds, 
			final Long timestamp) {
		Set<PersonGroupBean> permittedGroups = Sets.newHashSet();
		DateTimeJsonFilter shedule = new DateTimeJsonFilter();
		final Date date = new Date();
		for(PermitBean permit : daoGetPermitBeansByDeviceGroupIdOnDeviceGroup(deviceGroupId)){
			if(ignoreSchedule || shedule.apply(date,permit.getSchedule())){
				permittedGroups.addAll(daoChildListByParentForPersonGroup(permit.getPersonGroupId()));
			}
		}
		Set<PersonBean> persons = Sets.newHashSet();
		for(PersonGroupBean group : permittedGroups){
			persons.addAll(daoGetPersonsOfGroup(group.getId()));
		}
		final Set<Integer> excludeIds = Sets.newHashSet(MoreObjects.firstNonNull(excludePersonIds, Collections.<Integer>emptySet()));

		output.addAll( Sets.filter(persons, 
				new Predicate<PersonBean>() {		
			@Override
			public boolean apply(PersonBean input) {
				Date expiryDate = input.getExpiryDate();
				return ! excludeIds.contains(input.getId()) /** 过滤所有有效期失效的用户 */
						&& (timestamp == null  || input.getUpdateTime().getTime() > timestamp) 
						&& (null== expiryDate ? true : expiryDate.after(date));
			}
		}));
		for(DeviceGroupBean group:daoChildListByParentForDeviceGroup(deviceGroupId)){
			if(group.getId() != deviceGroupId){
				// exclude self
				getPersonsPermittedOnDeviceGroup(output,group.getId(),ignoreSchedule,excludePersonIds,timestamp);
			}

		}
	}
	/**
	 * 返回在指定设备上允许通行的所有人员记录<br>
	 * @param deviceId 设备ID
	 * @param ignoreSchedule 是否忽略时间过滤器(fl_permit.schedule字段)的限制
	 * @param excludePersonIds 要排除的人员记录id,可为{@code null}
	 * @param timestamp 不为{@code null}时返回fl_person.expiry_date大于指定时间戳的所有fl_person记录
	 * @return 返回的用户对象列表中，过滤所有有效期失效的用户<br>
	 */
	protected Set<PersonBean> 
	daoGetPersonsPermittedOnDevice(int deviceId, boolean ignoreSchedule, List<Integer> excludePersonIds, final Long timestamp) {
		Set<PersonBean> output = new HashSet<>();
		DeviceBean deviceBean = daoGetDeviceChecked(deviceId);
		getPersonsPermittedOnDeviceGroup(output,deviceBean.getGroupId(),ignoreSchedule,excludePersonIds,timestamp);
		return output;
	}
	/**
	 * 返回在指定设备上允许通行的所有人员记录<br>
	 * @param deviceId 设备ID
	 * @param ignoreSchedule 是否忽略时间过滤器(fl_permit.schedule字段)的限制
	 * @param excludePersonIds 要排除的人员记录id,可为{@code null}
	 * @param timestamp 不为{@code null}时返回fl_person.expiry_date大于指定时间戳的所有fl_person记录
	 * @return 返回的用户组--用户对象列表映射，过滤所有有效期失效的用户<br>
	 * @see #daoGetPersonsPermittedOnDevice(int, boolean, List, Long)
	 */
	protected Map<PersonGroupBean, List<PersonBean>> 
	daoGetPersonsPermittedOnDeviceByGroup(int deviceId, boolean ignoreSchedule, List<Integer> excludePersonIds, Long timestamp) {
		Set<PersonBean> persons = daoGetPersonsPermittedOnDevice(deviceId, ignoreSchedule, excludePersonIds, timestamp);
		ConcurrentMap<PersonGroupBean, List<PersonBean>> m = Maps.newConcurrentMap();
		for(PersonBean personBean:persons){
			List<PersonBean> o = m.putIfAbsent(daoGetPersonGroup(personBean.getGroupId()),Lists.newArrayList(personBean));
			if(null != o){
				o.add(personBean);
			}
		}
		return m;
	}
	/**
	 * 返回在指定设备上允许通行的所有人员记录<br>
	 * @param deviceId 设备ID
	 * @param ignoreSchedule 是否忽略时间过滤器(fl_permit.schedule字段)的限制
	 * @param excludePersonIds 要排除的人员记录id,可为{@code null}
	 * @param timestamp 不为{@code null}时返回fl_person.expiry_date大于指定时间戳的所有fl_person记录
	 * @return 以用户组为单位返回用户ID列表,每条记录的格式为 用户组ID:用户ID列表(,分隔),比如 '42:307,229,15' 代表用户组42下的用户ID列表'307,229,15'<br>
	 * 				 过滤所有有效期失效的用户 
	 * @see #daoGetPersonsPermittedOnDeviceByGroup(int, boolean, List, Long)
	 */
	protected List<String> 
	daoGetPersonsPermittedOnDeviceByGroupId(int deviceId, boolean ignoreSchedule, List<Integer> excludePersonIds, Long timestamp) {
		Map<PersonGroupBean, List<PersonBean>> m = daoGetPersonsPermittedOnDeviceByGroup(deviceId, ignoreSchedule, excludePersonIds, timestamp);
		ArrayList<String> c = Lists.newArrayList();
		for(Entry<PersonGroupBean, List<PersonBean>> entry:m.entrySet()){
			c.add(String.valueOf(entry.getKey().getId()) + ":" + Joiner.on(',').join(Lists.transform(entry.getValue(), daoCastPersonToPk)));
		}
		return c;
	}
	private PersonDataPackage daoGetPersonDataPackage(PersonBean personBean,String sdkVersion,int deviceId, boolean excludeExpired, PermitBean permitBean){
		if(null != personBean){
			if(!excludeExpired || !daoIsDisable(personBean)){
				if(null == permitBean){
					permitBean = daoGetPersonPermit(deviceId, personBean.getId());
				}
				// 判断在设备上是否有通行权限
				if(null != permitBean ){
					List<FeatureBean> featureBeans = daoGetFeaturesByPersonIdAndSdkVersion(personBean.getId(), sdkVersion);
					return new PersonDataPackage(personBean, permitBean.getSchedule(), permitBean.getPassLimit(), featureBeans);
				}
			}
		}
		return null;
	}
	/**
	 * 返回可在指定设备上通过的用户ID列表对应的用户数据包
	 * @param personIds
	 * @param sdkVersion 算法(SDK)版本号
	 * @param deviceId 设备ID
	 * @return 返回的用户数据包列表中,过滤所有有效期失效和无通行权限的用户<br>
	 */
	protected List<PersonDataPackage> daoLoadPersonDataPackages(List<Integer> personIds,String sdkVersion,int deviceId){
		checkArgument(null != sdkVersion,"sdkVersion is null");
		personIds = MoreObjects.firstNonNull(personIds, Collections.<Integer>emptyList());
		List<PersonDataPackage> output = Lists.newLinkedList();
		for(Integer personId : personIds){
			PersonDataPackage personDataPackage = daoGetPersonDataPackage(daoGetPerson(personId),sdkVersion,deviceId, true, null);
			if(null != personDataPackage){
				output.add(personDataPackage);
			}
		}
		return output;
	}
	/**
	 * 返回可在指定设备上通过的(同一组)用户ID列表对应的用户数据包
	 * @param personIds 在同一用户组的用户ID列表,使用列表中第一个用户记录来计算通行权限
	 * @param sdkVersion 算法(SDK)版本号
	 * @param deviceId 设备ID
	 * @return 返回的用户数据包列表中,过滤所有有效期失效和无通行权限的用户<br>
	 */
	protected List<PersonDataPackage> daoLoadPersonDataPackagesInSameGroup(List<Integer> personIds,String sdkVersion,int deviceId){
		checkArgument(null != sdkVersion,"sdkVersion is null");
		List<PersonDataPackage> output = Lists.newLinkedList();
		if(null != personIds &&  !personIds.isEmpty() && null != personIds.get(0)){
			/* 使用列表中第一个用户记录来计算通行权限 */
			PermitBean permitBean = daoGetPersonPermit(deviceId, personIds.get(0));
			if(null != permitBean){
				for(Integer personId : personIds){
					PersonDataPackage personDataPackage = 
							daoGetPersonDataPackage(daoGetPerson(personId),sdkVersion,deviceId, true, permitBean);
					if(null != personDataPackage){
						output.add(personDataPackage);
					}
				}
			}
		}
		return output;
	}
	/**
	 * 返回在指定设备上允许通行的所有用户数据包<br>
	 * @param deviceId 设备ID
	 * @param ignoreSchedule 是否忽略时间过滤器(fl_permit.schedule字段)的限制
	 * @param excludePersonIds 要排除的人员记录id,可为{@code null}
	 * @param timestamp 不为{@code null}时返回fl_person.expiry_date大于指定时间戳的所有fl_person记录
	 * @param sdkVersion 算法(SDK)版本号
	 * @return 返回的用户数据包列表中，过滤所有有效期失效的用户<br>
	 */
	protected List<PersonDataPackage> 
	daoLoadPersonDataPackagesPermittedOnDevice(int deviceId, boolean ignoreSchedule, List<Integer> excludePersonIds, Long timestamp,String sdkVersion){
		checkArgument(null != sdkVersion,"sdkVersion is null");
		Map<PersonGroupBean, List<PersonBean>> m = 
				daoGetPersonsPermittedOnDeviceByGroup(deviceId,ignoreSchedule,excludePersonIds,timestamp);
		List<PersonDataPackage> output = Lists.newLinkedList();
		for(List<PersonBean> persons : m.values()){
			List<PersonDataPackage> packages = daoLoadPersonDataPackagesInSameGroup(
					Lists.transform(persons,daoCastPersonToPk),sdkVersion,deviceId);
			if(null != packages){
				output.addAll(packages);
			}
		}
		return output;
	}
	/**
	 * 返回在指定设备上允许通行的所有特征记录
	 * @param deviceId 设备ID
	 * @param ignoreSchedule 是否忽略时间过滤器(fl_permit.schedule字段)的限制
	 * @param sdkVersion 特征版本号
	 * @param excludeFeatureIds 要排除的特征记录id(MD5),可为{@code null}
	 * @param timestamp 不为{@code null}时返回大于指定时间戳的所有fl_feature记录
	 * @return 特征记录集合
	 */
	protected Set<FeatureBean> 
	daoGetFeaturesPermittedOnDevice(int deviceId,boolean ignoreSchedule, String sdkVersion,final Collection<String> excludeFeatureIds, 
			final Long timestamp) {
			checkArgument(!Strings.isNullOrEmpty(sdkVersion),"sdkVersion is null or empty");
			
			Set<FeatureBean> features = Sets.newHashSet();
			for(PersonBean person:daoGetPersonsPermittedOnDevice(deviceId, ignoreSchedule, null, null)){
				features.addAll(daoGetFeaturesByPersonIdAndSdkVersion(person.getId(),sdkVersion));
			}
			return Sets.filter(features,new Predicate<FeatureBean>() {
				Set<String> excludeIds = Sets.newHashSet(MoreObjects.firstNonNull(excludeFeatureIds, Collections.<String>emptySet()));
				@Override
				public boolean apply(FeatureBean input) {
					return ! excludeIds.contains(input.getMd5()) && (timestamp == null  || input.getUpdateTime().getTime() > timestamp);
				}
			});
	}
	/**
	 * 添加人脸特征数据到数据库<br>
	 * 如果用户的特征记录数量超过限制，且没有开启自动更新机制则抛出异常,
	 * 如果已有特征数量超过限制，且开启了自动特征更新机制则删除最老的记录,确保记录总数不超过限制
	 * @param feature 人脸特征数据
	 * @param featureVersion SDK版本号
	 * @param refPersonByPersonId 所属的人员记录
	 * @param impFaceByFeatureMd5 关联的人脸信息记录
	 * @return 特征记录对象
	 * @throws DuplicateRecordException
	 */
	protected FeatureBean daoAddFeature(ByteBuffer feature,String featureVersion, 
			PersonBean refPersonByPersonId, Collection<FaceBean> impFaceByFeatureMd5) throws DuplicateRecordException{
		FEATURE_CONFIG.checkSdkVersion(featureVersion);
		boolean removeOld = false;
		List<FeatureBean> features = null;
		if(null != refPersonByPersonId && refPersonByPersonId.getId() != null){
			Integer personId = refPersonByPersonId.getId();
			features = daoGetFeaturesByPersonIdAndSdkVersion(personId,featureVersion);
			int count = features.size();
			int limitCount = FEATURE_CONFIG.getFeatureLimitPerPerson(featureVersion);
			// 如果用户的特征记录数量超过限制，且没有开启自动更新机制则抛出异常
			checkState(count < limitCount || FEATURE_CONFIG.featureAutoUpdateEnabled(),
					"person(id=%s)'s  %s feature count  exceed max limit(%s)",personId,featureVersion,limitCount);
			// 如果已有特征数量超过限制，且开启了自动特征更新机制则删除最老的记录
			if(count >= limitCount && FEATURE_CONFIG.featureAutoUpdateEnabled()){
				removeOld = true;
			}
		}
		FeatureBean newFeature = daoAddFeature(daoMakeFeature(feature, featureVersion), refPersonByPersonId, impFaceByFeatureMd5, null);
		// 放在成功添加记录之后再执行删除，以防止因为添加特征抛出异常而导致发送错误的通知消息
		if(removeOld){
			int limitCount = FEATURE_CONFIG.getFeatureLimitPerPerson(featureVersion);			
			// 以update_time字段排序,删除最旧的记录
			Collections.sort(features, RowMetaData.getMetaData(FeatureBean.class).comparatorOf(FL_FEATURE_ID_UPDATE_TIME,/** 降序 */true));
			for(int i = limitCount-1;i < features.size();++i){
				daoDeleteFeature(features.get(i).getMd5(), true);
				logger.info("AUTOUPDATE:remove feature [{}] and associated image",features.get(i).getMd5());
			}
		}
		return newFeature;
	}
	protected FeatureBean daoAddFeature(ByteBuffer feature,String featureVersion,PersonBean personBean,Map<ByteBuffer, FaceBean> faceInfo, DeviceBean deviceBean) throws DuplicateRecordException{
		if(null != faceInfo){
			for(Entry<ByteBuffer, FaceBean> entry:faceInfo.entrySet()){
				ByteBuffer imageBytes = entry.getKey();
				FaceBean faceBean = entry.getValue();
				daoAddImage(imageBytes, deviceBean, Arrays.asList(faceBean), null);
			}
		}
		return daoAddFeature(feature, featureVersion, personBean, null == faceInfo?null:faceInfo.values());
	}

	/**
	 * 删除指定的特征
	 * @param featureMd5
	 * @param deleteImage 为{@code true}则删除关联的 image记录(如果该图像还关联其他特征则不删除)
	 * @return 返回删除的特征记录关联的图像(image)记录的MD5
	 */
	protected List<String> daoDeleteFeature(final String featureMd5,boolean deleteImage){
		List<String> imageKeys = daoGetImageKeysImportedByFeatureMd5(featureMd5);
		if(deleteImage){
			for(Iterator<String> itor = imageKeys.iterator();itor.hasNext();){
				String md5 = itor.next();
				// 如果该图像还关联其他特征则不删除
				if(!Iterables.tryFind(daoGetFaceBeansByImageMd5OnImage(md5), new Predicate<FaceBean>(){

					@Override
					public boolean apply(FaceBean input) {
						return !featureMd5.equals(input.getFeatureMd5());
					}}).isPresent()){
					daoDeleteImage(md5);	
					itor.remove();
				}
			}
		}else{
			daoDeleteFaceBeansByFeatureMd5OnFeature(featureMd5);
		}
		daoDeleteFeature(featureMd5);
		return imageKeys;
	}
	protected List<String> daoDeleteFeatureChecked(final String featureMd5,boolean deleteImage){
		if(!Strings.isNullOrEmpty(featureMd5)){
			checkArgument(daoExistsFeature(featureMd5),"INVALID feature id %s",featureMd5);
			return daoDeleteFeature(featureMd5,true);
		}
		return Collections.emptyList();
	}
	protected int daoDeleteAllFeaturesByPersonId(Integer personId,boolean deleteImage){
		int count = 0;
		for(FeatureBean featureBean: daoGetFeatureBeansByPersonIdOnPerson(personId)){
			daoDeleteFeature(featureBean.getMd5(),deleteImage);
			++count;
		}
		return count;
	}
	protected Integer daoGetDeviceIdOfFeature(String featureMd5){
		for(String imageMd5: daoGetImageKeysImportedByFeatureMd5(featureMd5)){
			ImageBean imageBean = daoGetImage(imageMd5);
			if(null !=imageBean){
				return imageBean.getDeviceId();
			}
		}
		return null;
	}
	protected PersonBean daoSetRefPersonOfFeature(String featureMd5,Integer personId){
		PersonBean personBean = daoGetPerson(personId);
		FeatureBean featureBean = daoGetFeature(featureMd5);
		return (null == personBean || null == featureBean)
				? null
				: daoSetReferencedByPersonIdOnFeature(featureBean, personBean);
	}

	protected List<String> daoGetImageKeysImportedByFeatureMd5(String featureMd5){
		return Lists.transform(daoGetFaceBeansByFeatureMd5OnFeature(featureMd5), this.daoCastFaceToImageMd5);
	}
	protected PersonBean daoReplaceFeature(Integer personId,String featureMd5,boolean deleteImage){		
		daoDeleteAllFeaturesByPersonId(personId, deleteImage);
		return daoSetRefPersonOfFeature(featureMd5,personId);
	}

	protected int daoSavePerson(Map<ByteBuffer,PersonBean> persons) throws DuplicateRecordException {
		if(null == persons ){
			return 0;
		}
		int count = 0;
		PersonBean personBean ;
		for(Entry<ByteBuffer, PersonBean> entry:persons.entrySet()){
			personBean = daoSavePerson(entry.getValue(),daoAddImage(entry.getKey(),null,null,null),null);
			if(null != personBean){++count;}
		}
		return count;
	}
	/**
	 * 保存人员记录<br>
	 * 如果提供了新的身份照片({@code idPhotoBean}不为{@code null}),则删除旧照片用新照片代替
	 * @param personBean
	 * @param idPhotoBean 新的身份照片
	 * @param featureBean
	 * @return personBean
	 */
	protected PersonBean daoSavePerson(PersonBean personBean, ImageBean idPhotoBean,
			Collection<FeatureBean> featureBean) {
		// delete old photo if exists
		if(idPhotoBean != null){
			daoDeleteImage(daoGetReferencedByImageMd5OnPerson(personBean));
		}
		return daoSavePerson(personBean, idPhotoBean, null, null, featureBean, null);
	}

	protected PersonBean daoSavePerson(PersonBean bean, ByteBuffer idPhoto, FeatureBean featureBean,
			DeviceBean deviceBean) throws DuplicateRecordException {
		ImageBean imageBean = daoAddImage(idPhoto, deviceBean, null, null);
		return daoSavePerson(bean, imageBean, featureBean == null ? null : Arrays.asList(featureBean));
	}

	protected PersonBean daoSavePerson(PersonBean bean, ByteBuffer idPhoto, ByteBuffer feature,
			String featureVersion, Map<ByteBuffer, FaceBean> faceInfo, DeviceBean deviceBean) throws DuplicateRecordException {
		return daoSavePerson(bean, idPhoto, daoAddFeature(feature, featureVersion, bean, faceInfo, deviceBean), deviceBean);
	}

	/**
	 * 
	 * @param personBean 人员信息对象
	 * @param idPhoto 标准照图像
	 * @param feature 人脸特征数据
	 * @param featureVersion 特征(SDk)版本号
	 * @param featureImage 提取特征源图像,为null 时,默认使用idPhoto
	 * @param faceBean 人脸位置对象,为null 时,不保存人脸数据,忽略featureImage
	 * @param deviceBean featureImage来源设备对象
	 * @return personBean
	 * @throws DuplicateRecordException 
	 */
	protected PersonBean daoSavePerson(PersonBean personBean, ByteBuffer idPhoto, ByteBuffer feature,
			String featureVersion, ByteBuffer featureImage, FaceBean faceBean, DeviceBean deviceBean) throws DuplicateRecordException {
		List<FaceBean> faceList = faceBean == null ? null : Arrays.asList(faceBean);
		if (Judge.isEmpty(featureImage)){
			featureImage = idPhoto;
		}
		if(!Judge.isEmpty(idPhoto)){
			/** 保存标准照 */			
			ImageBean imageBean = daoAddImage(idPhoto,deviceBean,featureImage == idPhoto ? faceList : null,null);
			personBean.setImageMd5(imageBean.getMd5());
		}
		FeatureBean featureBean = null;
		if (null != faceBean) {
			if (!Judge.isEmpty(featureImage) && featureImage != idPhoto) {				
				/** 保存特征的同时保存featureImage */ 
				featureBean = daoAddFeature(feature, featureVersion, personBean, ImmutableMap.of(featureImage, faceBean), deviceBean);
			}else{
				featureBean = daoAddFeature(feature, featureVersion, personBean, faceList);
			}
		}
		return daoSavePerson(personBean, null, featureBean, deviceBean);
	}
	
	private static  PersonGroupBean checkPersonGroup(PersonGroupBean personGroupBean) {
		if(personGroupBean != null){
			checkArgument(personGroupBean.getParent() == null || personGroupBean.getRootGroup() == null,
					"person group %s is top group,so the 'parent' field must be null");
		}
		return personGroupBean;
	}
	private static  DeviceGroupBean checkDeviceGroup(DeviceGroupBean deviceGroupBean) {
		if(deviceGroupBean != null){
			checkArgument(deviceGroupBean.getParent() == null || deviceGroupBean.getRootGroup() == null,
					"device group %s is top group,so the 'parent' field must be null");
		}
		return deviceGroupBean;
	}
	@Override
	protected PersonGroupBean daoSavePersonGroup(PersonGroupBean personGroupBean) throws RuntimeDaoException {
		return super.daoSavePersonGroup(checkPersonGroup(personGroupBean));
	}
	/**
	 * 删除personId指定的人员(person)记录及关联的所有记录
	 * @param personId
	 * @return 返回删除的记录数量
	 */
	@Override
	protected int daoDeletePerson(Integer personId) {
		PersonBean personBean = daoGetPerson(personId);
		if(null == personBean){
			return 0;
		}
		// 删除标准照
		daoDeleteImage(personBean.getImageMd5());
		daoDeleteAllFeaturesByPersonId(personId,true);
		return super.daoDeletePerson(personId);
	}
	protected int daoDeletePersonByPapersNum(String papersNum) {
		PersonBean personBean = daoGetPersonByIndexPapersNum(papersNum);
		return null == personBean ? 0 : daoDeletePerson(personBean.getId());
	}
	protected int daoDeletePersonByPapersNum(Collection<String> collection) {
		int count =0;
		if(null != collection){
			for(String papersNum:collection){
				count += daoDeletePersonByPapersNum(papersNum);
			}
		}
		return count;
	}
	protected int daoDeletePersonByMobilePhone(String mobilePhone) {
		PersonBean personBean = daoGetPersonByIndexMobilePhone(mobilePhone);
		return null == personBean ? 0 : daoDeletePerson(personBean.getId());
	}
	protected int daoDeletePersonsByMobilePhone(Collection<String> collection) {
		int count =0;
		if(null != collection){
			for(String papersNum:collection){
				count += daoDeletePersonByMobilePhone(papersNum);
			}
		}
		return count;
	}
	protected boolean daoIsDisable(PersonBean personBean){
		if(null == personBean){
			return true;
		}
		Date expiryDate = personBean.getExpiryDate();
		return null== expiryDate?false:expiryDate.before(new Date());
	}
	protected boolean daoIsDisable(int personId){
		PersonBean personBean = daoGetPerson(personId);
		return daoIsDisable(personBean);
	}
	protected  void daoSetPersonExpiryDate(PersonBean personBean,Date expiryDate){
		if(null != personBean){
			personBean.setExpiryDate(expiryDate);
			daoSavePerson(personBean);
		}
	}
	protected  void daoSetPersonExpiryDate(Collection<Integer> personIdList,Date expiryDate){
		if(null != personIdList){
			for(Integer personId : personIdList){
				daoSetPersonExpiryDate(daoGetPerson(personId),expiryDate);
			}
		}
	}
	protected List<Integer> daoLoadUpdatedPersons(Date timestamp, String where4Person, String where4Feature) {
		LinkedHashSet<Integer> updatedPersons = Sets.newLinkedHashSet(daoLoadPersonIdByUpdateTime(where4Person,timestamp));
		List<Integer> idList = Lists.transform(
				daoLoadFeatureByUpdateTime(where4Feature,timestamp),
				daoCastFeatureToPersonId);
		// 两个collection 合并去除重复
		@SuppressWarnings("unused")
		boolean b = Iterators.addAll(updatedPersons, Iterators.filter(idList.iterator(), Predicates.notNull()));
		return Lists.newArrayList(updatedPersons);
	}
	/**
	 * 创建管理边界<br>
	 * 设置fl_person_group.root_group和fl_device_group.root_group字段互相指向,
	 * 以事务操作方式更新数据库
	 * @param personGroupId 人员组id
	 * @param deviceGroupId 设备组id
	 * @throws ObjectRetrievalException 没有找到personGroupId或deviceGroupId指定的记录
	 */
	protected void daoBindBorder(final Integer personGroupId,final Integer deviceGroupId) throws ObjectRetrievalException{
		final PersonGroupBean personGroup = daoGetPersonGroupChecked(personGroupId);
		final DeviceGroupBean deviceGroup = daoGetDeviceGroupChecked(deviceGroupId);

		{
			// 解除与人员组已有的其他设备组绑定
			Integer rootId = personGroup.getRootGroup();
			if(rootId != null && rootId != deviceGroupId){
				DeviceGroupBean root = daoGetDeviceGroup(rootId);
				root.setRootGroup(null);
				daoSaveDeviceGroup(root);
			}
		}
		{
			// 解除与设备组已有的其他人员组绑定
			Integer rootId = deviceGroup.getRootGroup();
			if(rootId != null && rootId != deviceGroupId){
				PersonGroupBean root = daoGetPersonGroup(rootId);
				root.setRootGroup(null);
				daoSavePersonGroup(root);
			}
		}
		personGroup.setRootGroup(deviceGroup.getId());
		deviceGroup.setRootGroup(personGroup.getId());
		daoSavePersonGroup(personGroup);
		daoSaveDeviceGroup(deviceGroup);
	}
	/**
	 * 删除管理边界<br>
	 * 删除fl_person_group.root_group和fl_device_group.root_group字段的互相指向,设置为{@code null},
	 * 以事务操作方式更新数据库<br>
	 * 如果personGroupId和deviceGroupId不存在绑定关系则跳过
	 * @param personGroupId 人员组id
	 * @param deviceGroupId 设备组id
	 * @throws ObjectRetrievalException 没有找到personGroupId或deviceGroupId指定的记录
	 */
	protected void daoUnbindBorder(Integer personGroupId,Integer deviceGroupId) throws ObjectRetrievalException{
		final PersonGroupBean personGroup = daoGetPersonGroupChecked(personGroupId);
		final DeviceGroupBean deviceGroup = daoGetDeviceGroupChecked(deviceGroupId);
		if(deviceGroup.getId().equals(personGroup.getRootGroup())
				|| personGroup.getId().equals(deviceGroup.getRootGroup())){
			personGroup.setRootGroup(null);
			deviceGroup.setRootGroup(null);
			daoRunAsTransaction(new Runnable() {
				@Override
				public void run() {
					daoSavePersonGroup(personGroup);
					daoSaveDeviceGroup(deviceGroup);
				}
			});
		}
	}
	/**
	 * 返回personId所属的管理边界人员组id<br>
	 * 在personId所属组的所有父节点中自顶向下查找第一个{@code fl_person_group.root_group}字段不为空的人员组，返回此记录组id
	 * @param personId
	 * @return {@code fl_person_group.root_group}字段不为空的记录id,没有找到则返回{@code null}
	 * @throws ObjectRetrievalException 没有找到personId指定的记录
	 */
	protected Integer daoRootGroupOfPerson(Integer personId) throws ObjectRetrievalException{
		Integer groupId = daoGetPersonChecked(personId).getGroupId();
		return daoRootGroupOfPersonGroup(groupId);
	}
	/**
	 * 返回groupId指定人员组的管理边界人员组id<br>
	 * 在groupId指定人员组的所有父节点中自顶向下查找第一个{@code fl_person_group.root_group}字段不为空的人员组，返回此记录组id
	 * @param groupId 人员组id
	 * @return {@code fl_person_group.root_group}字段不为空的记录id,没有找到则返回{@code null}
	 */
	protected Integer daoRootGroupOfPersonGroup(Integer groupId) {
		if(groupId == null){
			return null;
		}
		// 循环引用检查
		instanceOf(IPersonGroupManager.class).checkCycleOfParent(groupId);
		List<PersonGroupBean> parents = daoListOfParentForPersonGroup(groupId);
		Optional<PersonGroupBean> top = Iterables.tryFind(parents, new Predicate<PersonGroupBean>() {

			@Override
			public boolean apply(PersonGroupBean input) {
				return input.getRootGroup()!=null;
			}
		});
		return top.isPresent() ? top.get().getId() : null;
	}
	/**
	 * 返回deviceId所属的管理边界设备组id<br>
	 * 在deviceId所属组的所有父节点中自顶向下查找第一个{@code fl_device_group.root_group}字段不为空的组，返回此记录id
	 * @param deviceId
	 * @return {@code fl_device_group.root_group}字段不为空的记录id,没有找到则返回{@code null}
	 * @throws ObjectRetrievalException 没有找到deviceId指定的记录
	 */
	protected Integer daoRootGroupOfDevice(Integer deviceId) throws ObjectRetrievalException{
		Integer groupId = daoGetDeviceChecked(deviceId).getGroupId();
		return daoRootGroupOfDeviceGroup(groupId);
	}
	/**
	 * 返回groupId指定设备组的管理边界设备组id<br>
	 * 在groupId指定设备组的所有父节点中自顶向下查找第一个{@code fl_device_group.root_group}字段不为空的组，返回此记录id
	 * @param groupId 设备组id
	 * @return {@code fl_device_group.root_group}字段不为空的记录id,没有找到则返回{@code null}
	 */
	protected Integer daoRootGroupOfDeviceGroup(Integer groupId){
		if(groupId == null){
			return null;
		}
		// 循环引用检查
		instanceOf(IDeviceGroupManager.class).checkCycleOfParent(groupId);
		List<DeviceGroupBean> parents = daoListOfParentForDeviceGroup(groupId);
		Optional<DeviceGroupBean> top = Iterables.tryFind(parents, new Predicate<DeviceGroupBean>() {

			@Override
			public boolean apply(DeviceGroupBean input) {
				return input.getRootGroup()!=null;
			}
		});
		return top.isPresent() ? top.get().getId() : null;
	}
	private LogBean checkLogBean(LogBean logBean){
        if(logBean.getPersonId()==null){
        	String featureId = logBean.getVerifyFeature();
        	if(Strings.isNullOrEmpty(featureId)){
        		/** 填未知用户ID */
        		PersonBean personBean = getDummyUser();
        		logBean.setPersonId(personBean.getId());
        	}else{        		
        		FeatureBean featureBean = checkNotNull(daoGetFeature(featureId),
        				"NOT FOUND valid person id caused by invalid feature id %s",featureId);
        		logBean.setPersonId(checkNotNull(featureBean.getPersonId(),
        				"NOT FOUND valid person id caused by fl_feature.person_id is null"));
        	}
        } /*else {
        	// 检查verifyFeature记录所属的person_id是否与log的person_id相等
        	String featureId = logBean.getVerifyFeature();
        	if(!Strings.isNullOrEmpty(featureId)){
        		FeatureBean featureBean = checkNotNull(dm.daoGetFeature(featureId),
        				"INVALID feature id %s",featureId);
        		checkArgument(logBean.getPersonId().equals(featureBean.getPersonId()),
        				"MISMATCH person id for VerifyFeature");
        	}
        }*/
        return logBean;
	}
	@Override
	protected LogBean daoAddLog(LogBean logBean) throws RuntimeDaoException, DuplicateRecordException {
        return super.daoAddLog(checkLogBean(logBean));
	}
	@Override
	protected LogBean daoAddLog(LogBean logBean, DeviceBean refDeviceByDeviceId, FaceBean refFaceByCompareFace,
			FeatureBean refFeatureByVerifyFeature, ImageBean refImageByImageMd5, PersonBean refPersonByPersonId)
			throws RuntimeDaoException, DuplicateRecordException {
		return super.daoAddLog(checkLogBean(logBean), refDeviceByDeviceId, refFaceByCompareFace, refFeatureByVerifyFeature,refImageByImageMd5,
				refPersonByPersonId);
	}
	/**
	 * 添加一条验证日志记录
	 * <br>{@code DEVICE_ONLY}
	 * @param logBean 日志记录对象
	 * @param faceBean 需要保存到数据库的提取人脸特征的人脸信息对象
	 * @param featureImage 需要保存到数据库的现场采集人脸特征的照片
	 * @throws DuplicateRecordException 数据库中存在相同记录
	 */
	protected LogBean	daoAddLog(LogBean logBean,FaceBean faceBean,ByteBuffer featureImage) throws DuplicateRecordException {
		if(logBean == null || faceBean == null || featureImage == null){
			return null;
		}
		PersonBean personBean = daoGetReferencedByPersonIdOnLog(logBean);
		DeviceBean deviceBean = daoGetReferencedByDeviceIdOnLog(logBean);
		daoAddImage(featureImage, deviceBean, 
				faceBean == null ? null : Arrays.asList(faceBean), null);
		return daoAddLog(logBean, deviceBean, faceBean, null, null,personBean);
	}
	enum ErrorCatalog{
		SERVICE,DEVICE,APPLICATION
	}
	private ErrorLogBean checkErrorLogBean(ErrorLogBean logBean){
        String catalog = checkNotNull(logBean.getCatalog(),"catalog field is null");
        Matcher machter = Pattern.compile("(\\w+)(/\\w+)+").matcher(catalog);
        checkArgument(machter.matches(),"INVALID catalog format [%s],such as [device/network] required",catalog);
        String level1 = machter.group(1);
        try {           
            ErrorCatalog.valueOf(level1.toUpperCase());			
		} catch (IllegalArgumentException e) {
			throw new IllegalArgumentException("INVALID catalog level 1:" + level1);
		}
        return logBean;
	}
	@Override
	protected ErrorLogBean daoAddErrorLog(ErrorLogBean errorLogBean)
			throws RuntimeDaoException, DuplicateRecordException {
		return super.daoAddErrorLog(checkErrorLogBean(errorLogBean));
	}

	private volatile PersonBean dummyUser;
	private PersonBean getDummyUser(){
		// double check
		if(dummyUser == null){
			synchronized (this) {
				if(dummyUser == null){
					PersonBean tmpl = PersonBean.builder().name(DUMMY_USER).build();
					List<PersonBean> users = daoLoadPersonUsingTemplate(tmpl, 1, -1);
					if(users.isEmpty()){
						dummyUser = daoSavePerson(tmpl);
					} else{
						dummyUser = users.get(0);
					}
				}				
			}
		}
		return dummyUser;
	}
	
	protected LogBean	daoAddLog(LogBean logBean,ByteBuffer faceImage,Integer deviceId) throws DuplicateRecordException {
		if(logBean == null){
			logBean = new LogBean();
		}
		if(faceImage == null){
			return null;
		}
		PersonBean person;
		if(logBean.getPersonId() == null){
			person = getDummyUser();
			logBean.setPersonId(person.getId());
		}else{
			person = daoGetPersonChecked(logBean.getPersonId());
		}
		
		DeviceBean deviceBean = daoGetDevice(deviceId);
		ImageBean imageBean = daoAddImage(faceImage, deviceBean, null, null);
		return daoAddLog(logBean, deviceBean, null, null, imageBean,person);
	}
	/**
	 * 设置 personId 指定的人员为禁止状态<br>
	 * @param personId 
	 * @param moveToGroupId 将用户移动到指定的用户组，为{@code null}则不移动
	 * @param deletePhoto 为{@code true}删除用户标准照
	 * @param deleteFeature 为{@code true}删除用户所有的人脸特征数据(包括照片)
	 * @param deleteLog 为{@code true}删除用户所有通行日志
	 */
	protected void daoDisablePerson(int personId, Integer moveToGroupId, 
			boolean deletePhoto, boolean deleteFeature, boolean deleteLog){
		PersonBean personBean = daoGetPerson(personId);
		if(personBean == null){
			return;
		}
		// 有效期设置为昨天
		Date yesterday = new Date(System.currentTimeMillis() - 86400000L);
		personBean.setExpiryDate(yesterday);
		if(moveToGroupId != null){
			personBean.setGroupId(moveToGroupId);
		}
		daoSavePerson(personBean);
		if(deletePhoto){
			// 删除标准照
			daoDeleteImage(personBean.getImageMd5());
		}
		if(deleteFeature){
			daoDeleteAllFeaturesByPersonId(personId,true);
		}
		if(deleteLog){
			daoDeleteLogBeansByPersonIdOnPerson(personId);
		}
	}
	/** 允许修改的fl_permit表字段ID */
	private static final HashSet<Integer> allowedPermitColumns=Sets.newHashSet(FL_PERMIT_ID_SCHEDULE,
			FL_PERMIT_ID_PASS_LIMIT);
	/**
	 * 修改指定记录的的字段值(String类型)<br>
	 * 如果记录不存在则创建deviceGroupId和personGroupId之间的MANY TO MANY 联接表(fl_permit)记录,
     * @param deviceGroupId 设备组id
	 * @param personGroupId 人员组id
	 * @param column 字段名，允许的字段名为'schedule','pass_limit'
	 * @param value 字段值
     * @return (fl_permit)记录
     */
	protected PermitBean daoSavePermit(int deviceGroupId,int personGroupId,String column, String value){
		int columnID = -1;
		if(column != null){
			columnID=RowMetaData.getMetaData(PermitBean.class).columnIDOf(column);
			checkArgument(columnID>=0,"invalid column name %s",column);
			checkArgument(allowedPermitColumns.contains(columnID),"column %s can't be modify",column);
		}
		PermitBean permitBean = daoGetPermit(deviceGroupId, personGroupId);
		if(permitBean == null){
			permitBean = PermitBean.builder()
				.deviceGroupId(deviceGroupId)
				.personGroupId(personGroupId)
				.build();
		}
		if(columnID >= 0){
			permitBean.setValue(columnID, value);
		}

		return daoSavePermit(permitBean);
	}
	/**
	 * 获取指定人脸特征关联的人脸特征记录
	 * @param imageMd5 图像数据的MD校验码,为空或{@code null}或记录不存在返回空表
	 * @return 特征记录id列表
	 */
	protected List<String> daoGetFeaturesOfImage(String imageMd5){
		if(Strings.isNullOrEmpty(imageMd5)){
			return Collections.emptyList();
		}
		List<FaceBean> faces = daoGetFaceBeansByImageMd5OnImage(imageMd5);
		Iterable<String> features = Iterables.filter(Lists.transform(faces, daoCastFaceToFeatureMd5),Predicates.notNull());
		return Lists.newArrayList(features);
	}
	/**
	 * 获取指定人脸特征关联的人脸记录
	 * @param featureMd5 人脸特征记录id(MD校验码),为空或{@code null}或记录不存在返回空表
	 * @return {@link FaceBean}列表
	 */
	protected List<FaceBean> daoGetFacesOfFeature(String featureMd5){
		if(Strings.isNullOrEmpty(featureMd5)){
			return Collections.emptyList();
		}
		return daoGetFaceBeansByFeatureMd5OnFeature(featureMd5);
	}
	/**
	 * 获取指定图像关联的人脸记录
	 * @param imageMd5 图像数据的MD校验码,为空或{@code null}或记录不存在返回空表
	 * @return {@link FaceBean}列表
	 */
	protected List<FaceBean> daoGetFacesOfImage(String imageMd5){
		if(Strings.isNullOrEmpty(imageMd5)){
			return Collections.emptyList();
		}
		return daoGetFaceBeansByImageMd5OnImage(imageMd5);
	}
	private static final Function<LogBean, LogBean> dateCaster = new Function<LogBean,LogBean>(){

		@Override
		public LogBean apply(LogBean bean) {
			if(null == bean){
				return null;
			}				
			Date v = bean.getVerifyTime(); 
			if(v != null){
				v = bean.getCreateTime();
			}
			if(v != null){
				Calendar calendar=Calendar.getInstance();
				calendar.setTime(v);
				calendar.set(Calendar.HOUR_OF_DAY, 0);
				calendar.set(Calendar.MINUTE, 0);
				calendar.set(Calendar.SECOND, 0);
				bean.setVerifyTime(calendar.getTime());					
			}
			return bean;
		}};
	/**
	 * 按天统计指定用户的通行次数<br>
	 * @param personId
	 * @param startDate 统计起始日期,可为{@code null}
	 * @param endDate 统计结束日期,可为{@code null}
	 * @return 返回统计结果，即每个有通行记录的日期(格式:yyyy-MM-dd)的通行次数
	 */
	protected Map<String, Integer> daoCountPersonLog(int personId,Date startDate,Date endDate){
		SimpleDateFormat formatter = new SimpleDateFormat(DATE_FORMATTER_STR);
		StringBuffer buffer = new StringBuffer("WHERE");
		buffer.append(" person_id=").append(personId);
		checkArgument(null == startDate || null == endDate || startDate.getTime() <=endDate.getTime(),"start must <= end");
		if(startDate != null){
			buffer.append(" and");
			buffer.append(String.format(" date(verify_time)>='%s'",formatter.format(startDate)));
		}
		if(endDate != null){
			buffer.append(" and");
			buffer.append(String.format(" date(verify_time)>='%s'",formatter.format(endDate)));
		}

		List<LogBean> logList = daoLoadLogByWhere(buffer.toString(), 1, -1);
		Lists.transform(logList, dateCaster);
		Map<String, Integer>stat=Maps.newHashMap();
		
		for(LogBean log:logList){
			Date d = log.getVerifyTime();			
			if(d != null){
				String key = formatter.format(d);
				Integer c = MoreObjects.firstNonNull(stat.get(key), 0);
				stat.put(key, c + 1);
			}
		}
		return stat;
	}
	/**
	 * 根据图像的MD5校验码返回图像数据
	 * @param imageMD5
	 * @return 二进制数据字节数组,如果数据库中没有对应的数据则返回null
	 */
	protected byte[] daoGetImageBytes(String imageMD5) throws IOException{
		StoreBean storeBean = daoGetStore(imageMD5);
		return null ==storeBean?null:BinaryUtils.getBytes(storeBean.getData());
	}
	/**
	 * 根据提供的主键ID,返回图像数据
	 * @param primaryKey 主键
	 * @param refType 主键引用类型
	 * @return 二进制数据字节数组,如果数据库中没有对应的数据则返回null
	 * @throws IOException
	 */
	protected byte[] daoGetImageBytes(String primaryKey,RefSrcType refType) throws IOException{
		ImageBean bean = daoGetImage(primaryKey,refType);
		return null == bean ? null : daoGetImageBytes(bean.getMd5());		
	}
	/**
	 * 根据提供的主键ID,返回图像数据记录
	 * @param primaryKey 主键
	 * @param refType 主键引用类型
	 * @return 图像数据记录
	 */
	protected ImageBean daoGetImage(String primaryKey,RefSrcType refType) {
		checkArgument(refType!=null,"refType is null");
		if(null == primaryKey){
			return null;
		}
		
		switch (MoreObjects.firstNonNull(refType,RefSrcType.DEFAULT)) {
		case PERSON:
		{
			PersonBean bean = daoGetPerson(Integer.valueOf(primaryKey));
			return null == bean ? null : daoGetImage(bean.getImageMd5());
		}
		case FACE:
		{
			FaceBean bean = daoGetFace(Integer.valueOf(primaryKey));
			return null == bean ? null : daoGetImage(bean.getImageMd5());
		}
		case FEATURE:
		{
			List<String> beans = daoGetImageKeysImportedByFeatureMd5(primaryKey);
			return beans.size() > 0 ? daoGetImage(beans.get(0)) : null;
		}
		case LOG:
		{
			LogBean logBean = daoGetLog(Integer.valueOf(primaryKey));
			// 获取现场采集图像数据的优先序为: compare_face,image_md5
			ImageBean imageBean =  null == logBean ? null : daoGetImage(logBean.getCompareFace() == null ? null : logBean.getCompareFace().toString(),RefSrcType.FACE);
			if(imageBean == null){
				return daoGetImage(logBean.getImageMd5());
			}
			return imageBean;
		}
		case LIGHT_LOG:
		{
			return daoGetImage(primaryKey,RefSrcType.LOG);
		}
		case IMAGE:
		case DEFAULT:
			return daoGetImage(primaryKey);
		default:
			return null;
		}		
	}
	/**
	 * 返回归一化处理的MAC地址
	 * @param mac
	 */
	private static final String normalizeMac(String mac){
		// mac地址保存为小写强制转小写查找
		return mac == null ? null : mac.toLowerCase();
	}
	private static final Function<DeviceBean,DeviceBean> normalizeDeviceBean = new Function<DeviceBean,DeviceBean>(){

		@Override
		public DeviceBean apply(DeviceBean input) {
			if(input == null || input.getMac() == null){
				return input;
			}
			int modified = input.getModified();
			input.setMac(normalizeMac(input.getMac()));
			input.setModified(modified);
			return input;
		}};
	@Override
	protected DeviceBean daoGetDeviceByIndexMac(String mac)
			throws RuntimeDaoException{
		return super.daoGetDeviceByIndexMac(normalizeMac(mac));
	}
	@Override
    protected DeviceBean daoSaveDevice(DeviceBean deviceBean)
	        throws RuntimeDaoException{
    	return super.daoSaveDevice(normalizeDeviceBean.apply(deviceBean));
	}

	@Override
	protected DeviceGroupBean daoSaveDeviceGroup(DeviceGroupBean deviceGroupBean) throws RuntimeDaoException {
		return super.daoSaveDeviceGroup(checkDeviceGroup(deviceGroupBean));
	}

	/**
	 * 个人信息脱敏转换器
	 */
	protected static final  Function<PersonBean, PersonBean> DESENSITIZATION_TRANSFORMER= new Function<PersonBean, PersonBean>(){

		@Override
		public PersonBean apply(PersonBean input) {
			if(input != null){
				PersonBean output = input.clone();
				if(null != output.getName()){
					output.setName(output.getName().replaceAll("\\S", "*"));
				}
				if(null != output.getPapersNum()){
					output.setPapersNum(output.getPapersNum().replaceAll("\\S", "*"));
				}
				if(null != output.getMobilePhone()){
					output.setMobilePhone(output.getMobilePhone().replaceAll("\\S", "*"));
				}
				return output;
			}
			return input;
		}};
			
	/**
	 * 个人信息脱敏
	 * @param input 输入数据
	 * @return 输出脱敏数据
	 */
	protected static final PersonBean desensitize(PersonBean input) {		
		return DESENSITIZATION_TRANSFORMER.apply(input);		
	}
	/**
	 * 个人信息脱敏
	 * @param input 输入数据
	 * @return 输出脱敏数据
	 */
	protected static final List<PersonBean> desensitize(List<PersonBean> input) {
		return input == null ? null : Lists.transform(input, DESENSITIZATION_TRANSFORMER);
	}
	
	/**
	 * 返回'name'字段为指定值的所有记录
	 * @param interfaceClass
	 * @param name
	 * @param filter 用于筛选记录的过滤器,可为{@code null}
	 * @return 返回结果结录列表,没找到则返回空表
	 */
	protected <B extends BaseBean,M extends TableManager<B>> 
	List<B> 
	daoGetBeansByName(Class<M>interfaceClass,String name, Predicate<B> filter){
		List<B> beans = instanceOf(interfaceClass).loadByWhereAsList(String.format("WHERE name='%s'",name));
		if(filter != null){
			return Lists.newArrayList(Iterables.filter(beans, filter));
		}
		return beans;
	}

	protected <B extends BaseBean,M extends TableManager<B>> 
	String
	daoGroupPathOf(Class<M>interfaceClass, Integer id){
		M manager = instanceOf(interfaceClass);
		try {
			@SuppressWarnings({ "unchecked" })
			List<B> parentList = (List<B>) interfaceClass.getMethod("listOfParent", Integer.class).invoke(manager, id);
			List<String> parentNames = Lists.transform(parentList, new ColumnGetter<String>("name"));
			return Joiner.on('/').skipNulls().join(parentNames);
		} catch (InvocationTargetException e) {
			Throwables.throwIfUnchecked(e.getTargetException());
			throw new RuntimeException(e.getTargetException());
		} catch (Exception e) {
			Throwables.throwIfUnchecked(e);
			throw new RuntimeException(e);
		}
	
	}
	protected <B extends BaseBean,M extends TableManager<B>> 
	String
	daoGroupPathOf(Class<M>interfaceClass,B group){
		return daoGroupPathOf(interfaceClass,(Integer)checkNotNull(group,"group is null").getValue("id"));
	}
	private <B extends BaseBean,M extends TableManager<B>> 
	void
	deleteElements(Class<M>interfaceClass, B child){
		M manager = instanceOf(interfaceClass);
		try {
			String elementTypeName = child.getClass().getSimpleName().replace("Group", "");
			String methodName = String.format("delete%ssByGroupId", elementTypeName);
			interfaceClass.getMethod(methodName, Integer.class).invoke(manager, child.getValue("id"));
		} catch (InvocationTargetException e) {
			Throwables.throwIfUnchecked(e.getTargetException());
			throw new RuntimeException(e.getTargetException());
		} catch (Exception e) {
			Throwables.throwIfUnchecked(e);
			throw new RuntimeException(e);
		} 
	}
	String
	daoPathOf(String tablename,Integer id){
		if("fl_device_group".equals(tablename)){
			return daoGroupPathOf(IDeviceGroupManager.class, id);
		}else if("fl_person_group".equals(tablename)){
			return daoGroupPathOf(IPersonGroupManager.class, id);
		}else{
			throw new IllegalArgumentException(String.format("INVALID tablename [%s],'fl_device_group','fl_person_group' required",tablename));
		}
	}
	/**
	 * 删除指定组下的所有子节点
	 * @param interfaceClass
	 * @param groupId
	 * @param deleteElement 是否删除组中的元素(人员/设备记录)
	 */
	protected <B extends BaseBean,M extends TableManager<B>> 
	void
	daoDeleteChilds(Class<M>interfaceClass, Integer groupId, final boolean deleteElement){
		M manager = instanceOf(interfaceClass);
		try {
			@SuppressWarnings("unchecked")
			List<B>childs = (List<B>) interfaceClass.getMethod("childListByParent", Integer.class).invoke(manager, groupId);
			for(B child:childs){
				if(deleteElement){
					deleteElements(interfaceClass,child);
				}
				manager.delete(child);
			}
		} catch (InvocationTargetException e) {
			Throwables.throwIfUnchecked(e.getTargetException());
			throw new RuntimeException(e.getTargetException());
		} catch (Exception e) {
			Throwables.throwIfUnchecked(e);
			throw new RuntimeException(e);
		} 
	}
	/**
	 * 删除指定组下的所有子节点
	 * @param interfaceClass
	 * @param groupId
	 * @param deleteElement 是否删除组中的元素(人员/设备记录)
	 */
	protected <B extends BaseBean,M extends TableManager<B>> 
	void
	daoDeleteChilds(Class<M>interfaceClass, B groupId, final boolean deleteElement){
		daoDeleteChilds(
				interfaceClass,
				(Integer)checkNotNull(groupId,"group is null").getValue("id"),
				deleteElement);
	}
	/**
	 * 删除指定组并删除指定组下的所有子节点
	 * @param interfaceClass
	 * @param groupId 组ID
	 * @param deleteElement deleteElement 是否删除组中的元素(人员/设备记录)
	 */
	protected <B extends BaseBean,M extends TableManager<B>> 
	void
	daoDeleteTree(Class<M>interfaceClass, Integer groupId, boolean deleteElement){
		M manager = instanceOf(interfaceClass);
		B bean = manager.loadByPrimaryKey(groupId);
		if(bean != null){
			daoDeleteChilds(interfaceClass,groupId, deleteElement);
			if(deleteElement){			
				deleteElements(interfaceClass,bean);
			}
			manager.delete(bean);
		}
	}
	/**
	 * 删除指定组并删除指定组下的所有子节点
	 * @param interfaceClass
	 * @param group 
	 * @param deleteElement deleteElement 是否删除组中的元素(人员/设备记录)
	 */
	protected <B extends BaseBean,M extends TableManager<B>> 
	void
	daoDeleteTree(Class<M>interfaceClass, B group, boolean deleteElement){
		daoDeleteTree(
				interfaceClass,
				(Integer)checkNotNull(group,"group is null").getValue("id"),
				deleteElement);
	}
	/**
	 * 获取{@code parent}指定的组下不重复的唯一名字
	 * @param interfaceClass
	 * @param name
	 * @param parent
	 * @param cleanMore
	 * @param deleteElement  是否删除组中的元素(人员/设备记录)
	 * @param found
	 */
	private <B extends BaseBean,M extends TableManager<B>> 
	String
	getUniqueName(Class<M>interfaceClass,final String name, final Integer parent,boolean cleanMore, boolean deleteElement, AtomicReference<B> found){
		int c = 0;
		String n = name;
		do{
			// 返回parent指定的组下所有指定名称的记录
			List<B> groups = daoGetBeansByName(interfaceClass,n, new Predicate<B>() {

				@Override
				public boolean apply(B input) {
					return Objects.equal(parent, input.getValue("parent"));
				}
			});
			if(groups.size() > 1){
				if(cleanMore){
					// 删除多余的同名记录
					for(int i = 1; i < groups.size();++i){
						B d = groups.get(i);
						daoDeleteTree(interfaceClass, d, deleteElement);
						logger.info("Delete duplicated group tree {}[{}] on {}",
								daoGroupPathOf(interfaceClass, d),
								d.getValue("id"),
								d.tableName());
					}
					if(found != null){
						found.set(groups.get(0));
					}
					return n;
				}else{
					n = String.format("%s(%d)", name,++c);
					continue;
				}
			}else if(groups.size() == 1){
				if(found != null){
					found.set(groups.get(0));
				}
				return n;
			}else{
				found.set(null);
				return n;
			}
		}while(true);
	}
	/**
	 * 创建组节点
	 * @param interfaceClass
	 * @param name
	 * @param parent
	 * @param cleanMore 当存在同名的多个节点时是否清除多余的节点只保留1个
	 * @param deleteElement 删除组时是否删除组下的元素(设备/人员)
	 * @return 组节点记录
	 */
	protected <B extends BaseBean,M extends TableManager<B>> 
	B
	daoCreateGroupByNameIfNoExist(Class<M>interfaceClass,String name,Integer parent, boolean cleanMore, boolean deleteElement){
		M manager = instanceOf(interfaceClass);
		AtomicReference<B> found = new AtomicReference<>();
		name = getUniqueName(interfaceClass,name,parent,cleanMore, deleteElement, found);
		if(found.get() == null){
			B group = manager.createBean();
			group.setValue("name", name);
			group.setValue("parent", parent);
			manager.save(group);
			logger.info("Create group {}[{}] on {}",
					daoGroupPathOf(interfaceClass, group),
					group.getValue("id"),
					group.tableName());
			return group;
		}
		logger.info("Exist group {}[{}] on {}",
				daoGroupPathOf(interfaceClass,found.get()),
				found.get().getValue("id"),found.get().tableName());
		return found.get();
	}
	/**
	 * 根据{@code cleanExistingTop}参数标志删除存在同名顶级组节点
	 * @param interfaceClass
	 * @param name 顶级组名
	 * @param cleanExistingTop 是否删除存在的同名顶级组节点
	 * @param deleteElement 是否删除组中的元素(人员/设备记录)
	 */
	private <B extends BaseBean,M extends TableManager<B>> 
	void
	deleteTopGroupIfExist(Class<M>interfaceClass, String name, boolean cleanExistingTop, boolean deleteElement){
		if(cleanExistingTop){
			// 删除存在的所有同名顶级组
			List<B> groups = daoGetBeansByName(interfaceClass,name,new Predicate<B>() {

				@Override
				public boolean apply(B input) {
					return input.getValue("parent") == null;
				}
			});
			for(B group:groups){
				daoDeleteTree(interfaceClass,group, deleteElement);
				logger.info("REMOVE top group {}[{}] on {}",group.getValue("name"),group.getValue("id"),group.tableName());
			}
		}
	}
	/**
	 * 创建顶级组节点
	 * @param interfaceClass
	 * @param name
	 * @param cleanMore 当存在同名的多个节点时是否清除多余的节点只保留1个
	 * @param deleteElement 删除组时是否删除组下的元素(设备/人员)
	 * @return 组节点记录
	 */
	protected <B extends BaseBean,M extends TableManager<B>> 
	B
	daoCreateTopGroupIfNoExist(Class<M>interfaceClass, String name, boolean cleanMore, boolean deleteElement){
		B group = daoCreateGroupByNameIfNoExist(interfaceClass,name,null, cleanMore, false);
		checkArgument(group.getValue("parent") == null, "group %s:%s is not top group",group.tableName(),name);
		return group;
	}
	
	/**
	 * 根据路径创建组节点
	 * @param interfaceClass
	 * @param path
	 * @param cleanMore 当存在同名的多个节点时是否清除多余的节点只保留1个
	 * @param deleteElement 删除组时是否删除组下的元素(设备/人员)
	 * @return 组节点记录
	 */
	protected <B extends BaseBean,M extends TableManager<B>> 
	B[]
	daoCreateGroupByPathIfNoExist(Class<M>interfaceClass,String path, boolean cleanMore, boolean deleteElement){
		String[] nodes = TopGroupInfo.parseNodes(path);
		@SuppressWarnings("unchecked")
		B[] nodeBeans = (B[]) Array.newInstance(instanceOf(interfaceClass).createBean().getClass(), nodes.length);
		if(nodes.length > 0){
			logger.info("Create path {}",Joiner.on('/').join(nodes));
			B bean = daoCreateTopGroupIfNoExist(interfaceClass, nodes[0], cleanMore, deleteElement);
			Integer parent = bean.getValue("id");
			nodeBeans[0] = bean;
			for(int i = 1; i < nodes.length; ++i){
				bean = daoCreateGroupByNameIfNoExist(interfaceClass, nodes[i], parent, cleanMore, deleteElement);
				nodeBeans[i] = bean;
				parent = bean.getValue("id");
			}
		}
		return nodeBeans;
	}
	protected <B extends BaseBean,M extends TableManager<B>> 
	List<B>
	daoGetGroupsByPath(final Class<M>interfaceClass,final String path){
		String[] nodes = TopGroupInfo.parseNodes(path);
		if(nodes.length > 0){
			return daoGetBeansByName(interfaceClass, nodes[nodes.length-1], new Predicate<B>() {

				@Override
				public boolean apply(B input) {
					return path.equals(daoGroupPathOf(interfaceClass,input));
				}
			});			
		}
		return Collections.emptyList();
	}
	protected 
	List<Integer>
	daoGetGroupIdsByPath(String tablename,String path){
		if("fl_device_group".equals(tablename)){
			return Lists.transform(daoGetGroupsByPath(IDeviceGroupManager.class, path), daoCastDeviceGroupToPk);
		}else if("fl_person_group".equals(tablename)){
			return Lists.transform(daoGetGroupsByPath(IPersonGroupManager.class, path), daoCastPersonGroupToPk);
		}else{
			throw new IllegalArgumentException(String.format("INVALID tablename [%s],'fl_device_group','fl_person_group' required",tablename));
		}
	}
	protected <B extends BaseBean,M extends TableManager<B>> 
	B
	daoGetGroupByPath(final Class<M>interfaceClass,final String path){
		String[] nodes = TopGroupInfo.parseNodes(path);
		if(nodes.length > 0){
			List<B> nodeBeans = daoGetBeansByName(interfaceClass, nodes[nodes.length-1], new Predicate<B>() {

				@Override
				public boolean apply(B input) {
					return path.equals(daoGroupPathOf(interfaceClass,input));
				}
			});

			switch(nodeBeans.size()){
			case 0:
				return null;
			case 1:
				return nodeBeans.get(0);
			default:
				throw new IllegalArgumentException(String.format("FOUND multi record in %s,path=%s",
						nodeBeans.get(0).tableName(),path));
			}
			
		}
		return null;
	}	
	/**
	 * 创建叶子节点
	 * @param interfaceClass
	 * @param parent
	 * @param name
	 * @param cleanMore
	 * @return 叶子节点记录
	 */
	protected <B extends BaseBean,M extends TableManager<B>> 
	B
	daoCreateLeafIfNoExist(Class<M>interfaceClass,int parent,String name, boolean cleanMore){
		checkArgument(!Strings.isNullOrEmpty(name),"name is null or empty");
		B bean;
		M manager = instanceOf(interfaceClass);
		B[] found = manager.loadByWhere(String.format("WHERE name='%s' AND parent=%d",name,parent));
		if(found.length > 0){
			if(found.length > 1 && cleanMore ){
				for(int i = 1; i < found.length; ++i){
					manager.delete(found[i]);
				}
			}
			bean = found[0];
			logger.info("Exist leaf group {}[{}] on {}",
					daoGroupPathOf(interfaceClass, bean),
					bean.getValue("id"),
					bean.tableName());
		}else{
			bean = manager.createBean();
			bean.setValue("name", name);
			bean.setValue("parent", parent);
			bean.setValue("leaf", 1);
			manager.save(bean);
			logger.info("Create leaf group {}[{}] on {}",
					daoGroupPathOf(interfaceClass, bean),
					bean.getValue("id"),
					bean.tableName());

		}
		return bean;
	}
	
	/**
	 * 创建叶子节点
	 * @param interfaceClass
	 * @param parent
	 * @param names
	 * @param cleanMore
	 * @return 叶子节点记录
	 */
	protected <B extends BaseBean,M extends TableManager<B>> 
	B[]
	daoCreateLeafBeansIfNoExist(Class<M>interfaceClass,int parent,String names, boolean cleanMore){
		List<String> list = MiscellaneousUtils.elementsOf(names);
		@SuppressWarnings("unchecked")
		B[] ids = (B[]) Array.newInstance(instanceOf(interfaceClass).createBean().getClass(),list.size());
		for(int i = 0 ; i < list.size(); ++i){
			ids[i] = daoCreateLeafIfNoExist(interfaceClass,parent,list.get(i),cleanMore);
		}
		return ids;
	}
	
	private void permitPath(PersonGroupBean permitPersonGroup,String devicePath){
		DeviceGroupBean deviceGroup = daoGetGroupByPath(IDeviceGroupManager.class, devicePath);
		if(deviceGroup != null){
			daoSavePermit(deviceGroup.getId(), permitPersonGroup.getId(), null, null);
			logger.info("Permit person group {}[{}] on device {}[{}]",
					daoGroupPathOf(IPersonGroupManager.class, permitPersonGroup),
					permitPersonGroup.getId(),
					devicePath,
					deviceGroup.getId());
		}else{
			logger.warn("NO FOUND device path {}",devicePath);
		}
	}
	/**
	 * 根据输入的{@link TopGroupInfo}对象创建顶级组(人员/设备)其下子节点<br>
	 * @param groupInfo
	 * @return 顶级人员组id
	 */
	protected int initTopGroup(TopGroupInfo groupInfo){
		checkArgument(groupInfo != null,"groupInfo is null");
		checkArgument(!Strings.isNullOrEmpty(groupInfo.getName()),"name is null or empty");
		groupInfo.normalize();
		deleteTopGroupIfExist(IDeviceGroupManager.class,groupInfo.getName(),groupInfo.isCleanExistingTop(), groupInfo.isDeleteElement());
		deleteTopGroupIfExist(IPersonGroupManager.class,groupInfo.getName(),groupInfo.isCleanExistingTop(), groupInfo.isDeleteElement());
		DeviceGroupBean topDeviceGroup = daoCreateTopGroupIfNoExist(IDeviceGroupManager.class, groupInfo.getName(), groupInfo.isClearMore(), groupInfo.isDeleteElement());
		PersonGroupBean topPersonGroup = daoCreateTopGroupIfNoExist(IPersonGroupManager.class, groupInfo.getName(), groupInfo.isClearMore(), groupInfo.isDeleteElement());
		daoBindBorder(topPersonGroup.getId(), topDeviceGroup.getId());
		logger.info("==BindBorder person top group {}[{}] VS device top person {}[{}]",
				topPersonGroup.getName(),
				topPersonGroup.getId(),
				topDeviceGroup.getName(),
				topDeviceGroup.getId());

		if(!groupInfo.getNodes().isEmpty()){
			logger.info("==============================");
			logger.info("====Create deive/permit groups pairs=====");
			logger.info("==============================");
			// 创建配对的设备组人员组并授权
			for(Entry<String, String> entry:groupInfo.getNodes().entrySet()){
				String path = groupInfo.fullPathOf( entry.getKey());
				String leafs = entry.getValue();
				DeviceGroupBean[] deviceGroupParentBeans = daoCreateGroupByPathIfNoExist(IDeviceGroupManager.class, path, groupInfo.isClearMore(), false);
				PersonGroupBean[] personGroupParentBeans = daoCreateGroupByPathIfNoExist(IPersonGroupManager.class, path, groupInfo.isClearMore(), false);
				checkState(deviceGroupParentBeans.length == personGroupParentBeans.length,"MISMATCH length between deviceGroupIds and personGroupIds");
				for(int i = 0; i < deviceGroupParentBeans.length; i++){
					daoSavePermit(deviceGroupParentBeans[i].getId(), personGroupParentBeans[i].getId(), null, null);
					logger.info("Permit person group {}[{}] ON device {}[{}]",
							daoGroupPathOf(IPersonGroupManager.class, personGroupParentBeans[i]),
							personGroupParentBeans[i].getId(),
							daoGroupPathOf(IDeviceGroupManager.class,deviceGroupParentBeans[i]),
							deviceGroupParentBeans[i].getId());
				}
				DeviceGroupBean[] deviceGroupLeafBeans = daoCreateLeafBeansIfNoExist(IDeviceGroupManager.class, deviceGroupParentBeans[deviceGroupParentBeans.length-1].getId(), leafs, groupInfo.isClearMore());
				PersonGroupBean[] personGroupLeftBeans = daoCreateLeafBeansIfNoExist(IPersonGroupManager.class, personGroupParentBeans[personGroupParentBeans.length-1].getId(), leafs, groupInfo.isClearMore());
				for(int i = 0; i < deviceGroupLeafBeans.length; ++i){
					daoSavePermit(deviceGroupLeafBeans[i].getId(), personGroupLeftBeans[i].getId(), null, null);
					logger.info("Permit person group {}[{}] on device {}[{}]",
							daoGroupPathOf(IPersonGroupManager.class, personGroupLeftBeans[i]),
							personGroupLeftBeans[i].getId(),
							daoGroupPathOf(IDeviceGroupManager.class,deviceGroupLeafBeans[i]),
							deviceGroupLeafBeans[i].getId());
				}
			}
		}
		if(!groupInfo.getPermits().isEmpty()){
			logger.info("==============================");
			logger.info("===========Create Permits==========");
			logger.info("==============================");
			// 对指定人员组授权指定的设备组
			for(Entry<String, String> entry:groupInfo.getPermits().entrySet()){
				String personGroupPath = groupInfo.fullPathOf(Strings.nullToEmpty(entry.getKey()));
				String deviceLeafs = Strings.nullToEmpty(entry.getValue());
				if(!Strings.isNullOrEmpty(deviceLeafs)){
					PersonGroupBean[] personGroupIds = daoCreateGroupByPathIfNoExist(IPersonGroupManager.class, personGroupPath, groupInfo.isClearMore(), false);
					PersonGroupBean permitPersonGroup = personGroupIds[personGroupIds.length - 1];
					String[] entries = deviceLeafs.split("[,]");
					if(entries.length > 0){
						String prefix = groupInfo.fullPathOf(entries[0]);
						if(entries.length == 2){
							if(entries[1].equals("*")){
								// 第二个元素为通配符则列出所有子节点
								DeviceGroupBean prefixDeviceGroup = daoGetGroupByPath(IDeviceGroupManager.class, prefix);
								if(prefixDeviceGroup != null){
									List<DeviceGroupBean> childs = daoGetSubDeviceGroup(prefixDeviceGroup.getId());
									List<String> childNames = Lists.newArrayList(Iterables.transform(childs, new Function<DeviceGroupBean,String>(){

										@Override
										public String apply(DeviceGroupBean input) {
											return input.getName();
										}}));
									childNames.add(0, entries[0]);
									entries = childNames.toArray(new String[0]);
								}else{
									logger.warn("NO FOUND device path {}",prefix);
									continue;
								}
							}
						}
						for(int i = 1; i < entries.length; ++i){
							if(!entries[i].isEmpty()){
								permitPath(permitPersonGroup, prefix + "/" + entries[i]);
							}
						}
						permitPath(permitPersonGroup,prefix);
					}
				}
			}
		}
		return topPersonGroup.getId();
	}
	
	protected PersonGroupBean daoGetGroupOfPerson(Integer id)throws RuntimeDaoException{
		PersonBean bean = daoGetPerson(id);
		return null == bean ? null : daoGetPersonGroup(bean.getGroupId());
	}
	protected DeviceGroupBean daoGetGroupOfDevice(Integer id)throws RuntimeDaoException{
		DeviceBean bean = daoGetDevice(id);
		return null == bean ? null : daoGetDeviceGroup(bean.getGroupId());
	}
	protected PersonGroupBean daoGetParentOfPersonGroup(Integer id)throws RuntimeDaoException{
		PersonGroupBean bean = daoGetPersonGroup(id);
		return null == bean ? null : daoGetPersonGroup(bean.getParent());
	}
	protected DeviceGroupBean daoGetParentOfDeviceGroup(Integer id)throws RuntimeDaoException{
		DeviceGroupBean bean = daoGetDeviceGroup(id);
		return null == bean ? null : daoGetDeviceGroup(bean.getParent());
	}
//	protected boolean hasNullField(BaseRow bean, int... columnIds) {
//		if(null != columnIds && 0 != columnIds.length && null != bean){
//			Object[] values = bean.asValueArray(columnIds);
//			return hasNull(values);
//		}
//		return false;
//	}
}
